#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "y.tab.h"
#include "tacs.h"
#include "astree.h"


extern table symbolsTable;

/*
void printInt(int v){
    printf("%d",v);
}
*/
// Safe assembly from the functions above.
char *printFunctionCode = 
".LC0_FORMAT_IO: \n"
"	.string	\"%d\" \n"
"	.text \n"
".globl __FNA__printVar \n"
"	.type	__FNA__printVar, @function \n"
"__FNA__printVar: \n"
"	pushl	%ebp \n"
"	movl	%esp, %ebp \n"
"	subl	$8, %esp \n"
"	movl	$.LC0_FORMAT_IO, %eax \n"
"	movl	8(%ebp), %edx \n"
"	movl	%edx, 4(%esp) \n"
"	movl	%eax, (%esp) \n"
"	call	printf \n"
"	leave \n"
"	ret   \n";

char *readFunctionCode = 
".globl __FNA__readVar \n"
"	.type	__FNA__readVar, @function \n"
"__FNA__readVar: \n"
"	pushl	%ebp \n"
"	movl	%esp, %ebp \n"
"	subl	$24, %esp \n"
"	movl	$.LC0_FORMAT_IO, %eax \n"
"	movl	8(%ebp), %edx \n"
"	movl	%edx, 4(%esp) \n"
"	movl	%eax, (%esp) \n"
"	call	__isoc99_scanf \n"
"	leave \n"
"	ret \n";


void assembleVariableDeclarations(TAC *list, FILE *out);


#define isLit(row) (row->type != SYMBOL_IDENTIFIER) ? \
     ( \
            ( ( (row->type == SYMBOL_LIT_TRUE) || (row->type == SYMBOL_LIT_FALSE)   ) ? "_V" : "$" ) \
     ) \
     : ""

char *accessVar(row *var) {
    char *ref = (char *) malloc(sizeof(char)*500);
    strcpy(ref,"");
    
    // If it is a literal value.
    if(var->type != SYMBOL_IDENTIFIER) {
        if((var->type == SYMBOL_LIT_TRUE) || (var->type == SYMBOL_LIT_FALSE)) {
            strcpy(ref,"_V");
        }
        else {
            strcpy(ref,"$");
        }
        strcat(ref,var->value);
    }
    // If it is a variable local to a function and not a temp.
    else if( (var->scope > 0) || (var->localI > -1) ){
        //strcat(ref,var->value);
        
        sprintf(ref,"%d", 4*(var->localI)+8);
        strcat(ref,"(%ebp)");
    }
    else {
        strcat(ref,var->value);
    }
    
    return ref;
}

void assembleCode(TAC *list, FILE *out) {
    // First, assemble all declarations as global.
    assembleVariableDeclarations(list,out);
    
    // Create special functions for output:
    printf("%s \n",printFunctionCode);
    printf("%s \n",readFunctionCode);

    TAC *t;
    int auxLblId = 0;
    int auxStringId = 0;
    
    int paramCount = 0;
    param *fArgs;
    row *arg;
    
    for(t = list; t != NULL; t = t->next) {
        switch (t->type) {
            case TAC_LABEL:
                fprintf(out,"\n.%s:",t->target->value);
                break;
            
            case TAC_MOVE:
                fprintf(out,"\n    movl %s   , %%eax",accessVar(t->target));
                fprintf(out,"\n    movl %%eax, %s",accessVar(t->op1));
                break;
            
            case TAC_REF:
                if(t->op1->localI > -1) {
                    fprintf(out,"\n    leal %s, %%eax", accessVar(t->op1));
                    fprintf(out,"\n    movl %%eax, %s", accessVar(t->target));
                }
                else {
                    fprintf(out,"\n    movl $%s, %s", t->op1->value, accessVar(t->target));
                }
                break;
                
            case TAC_DEREF:
                fprintf(out,"\n    movl	%s, %%eax",accessVar(t->op1));
	            fprintf(out,"\n    movl	(%%eax), %%eax");
	            fprintf(out,"\n    movl	%%eax, %s",accessVar(t->target)); 
                break;
            
            case TAC_ADD:
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op1));
                fprintf(out,"\n    addl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    movl %%eax, %s",accessVar(t->target));
                break;
            
            case TAC_SUB:
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op1));
                fprintf(out,"\n    subl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    movl %%eax, %s",accessVar(t->target));
                break;
            
            case TAC_MUL:
	            fprintf(out,"\n    movl %s, %%edx",accessVar(t->op1));
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    imull %%edx, %%eax");
                fprintf(out,"\n    movl %%eax, %s",accessVar(t->target));
                break;
            
            case TAC_DIV:
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op1));
                fprintf(out,"\n    movl %s, %%edx",accessVar(t->op2));
                fprintf(out,"\n    movl	%%edx, 28(%%esp)");
                fprintf(out,"\n    movl	%%eax, %%edx");
                fprintf(out,"\n    sarl	$31, %%edx");
                fprintf(out,"\n    idivl	28(%%esp)");
                fprintf(out,"\n    movl %%eax, %s",accessVar(t->target));
                break;
            
            case TAC_LE:
                fprintf(out,"\n    movl %s, %%edx",accessVar(t->op1));
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    cmpl	%%eax, %%edx");
                fprintf(out,"\n    setle    %%al");
                fprintf(out,"\n    movzbl	%%al, %%eax");
                fprintf(out,"\n    movl	%%eax, %s", accessVar(t->target));
                break;
                
            case TAC_GE:
                fprintf(out,"\n    movl %s, %%edx",accessVar(t->op1));
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    cmpl	%%eax, %%edx");
                fprintf(out,"\n    setge    %%al");
                fprintf(out,"\n    movzbl	%%al, %%eax");
                fprintf(out,"\n    movl	%%eax, %s",accessVar(t->target));
                break;
            
            case TAC_EQ:
                fprintf(out,"\n    movl %s, %%edx",accessVar(t->op1));
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    cmpl	%%eax, %%edx");
                fprintf(out,"\n    sete    %%al");
                fprintf(out,"\n    movzbl	%%al, %%eax");
                fprintf(out,"\n    movl	%%eax, %s",accessVar(t->target));
                break;
                
            case TAC_NE:
                fprintf(out,"\n    movl %s, %%edx",accessVar(t->op1));
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    cmpl	%%eax, %%edx");
                fprintf(out,"\n    setne    %%al");
                fprintf(out,"\n    movzbl	%%al, %%eax");
                fprintf(out,"\n    movl	%%eax, %s",accessVar(t->target));
                break;
                
            case TAC_G:
                fprintf(out,"\n    movl %s, %%edx",accessVar(t->op1));
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    cmpl	%%eax, %%edx");
                fprintf(out,"\n    setg    %%al");
                fprintf(out,"\n    movzbl	%%al, %%eax");
                fprintf(out,"\n    movl	%%eax, %s",accessVar(t->target));
                break;

            case TAC_L:
                fprintf(out,"\n    movl %s, %%edx",accessVar(t->op1));
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    cmpl	%%eax, %%edx");
                fprintf(out,"\n    setl    %%al");
                fprintf(out,"\n    movzbl	%%al, %%eax");
                fprintf(out,"\n    movl	%%eax, %s",accessVar(t->target));
                break;
                
            case TAC_AND:
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op1));
                fprintf(out,"\n    testl	%%eax, %%eax");
                fprintf(out,"\n    je	.L%d",auxLblId);
                
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    testl	%%eax, %%eax");
                fprintf(out,"\n    je	.L%d",auxLblId);
                
                fprintf(out,"\n    movl $1, %s",accessVar(t->target));
                fprintf(out,"\n    jmp	.L%d",auxLblId+1);
                
                fprintf(out,"\n.L%d:",auxLblId);
                fprintf(out,"\n    movl $0, %s",accessVar(t->target));
                
                auxLblId++;
                fprintf(out,"\n.L%d:",auxLblId);
                auxLblId++;
                break;
                
            case TAC_OR:
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op1));
                fprintf(out,"\n    testl	%%eax, %%eax");
                fprintf(out,"\n    jne	.L%d",auxLblId);
                
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                fprintf(out,"\n    testl	%%eax, %%eax");
                fprintf(out,"\n    je	.L%d",auxLblId+1);
                
                
                fprintf(out,"\n.L%d:",auxLblId);
                fprintf(out,"\n    movl $1, %s",accessVar(t->target));
                fprintf(out,"\n    jmp	.L%d",auxLblId+2);
                
                fprintf(out,"\n.L%d:",auxLblId+1);
                fprintf(out,"\n    movl $0, %s",accessVar(t->target));
                
                fprintf(out,"\n.L%d:",auxLblId+2);
                
                auxLblId += 3;
                break;
            
            case TAC_BEGINFUN:
                fprintf(out,"\n.globl %s",t->target->value);
	            fprintf(out,"\n.type    %s, @function",t->target->value);
                fprintf(out,"\n%s:",t->target->value);
                fprintf(out,"\n     pushl	%%ebp");
                fprintf(out,"\n    movl %%esp, %%ebp");
                fprintf(out,"\n    subl $8, %%esp");
                
                paramCount = 0;
                fArgs = t->target->args;
                while(fArgs != NULL) {
                    arg = fArgs->v;
                    if(arg != NULL) {
                        //fprintf(out,"\n    movl %d(%%esp), %%eax",(paramCount*4)+8);
                        //fprintf(out,"\n    movl %%eax, %s",arg->value);
                        
                        //fprintf(out,"\n    movl %s, %%eax",accessVar(arg));
                        //fprintf(out,"\n    movl %%eax, %d(%%ebp)",((paramCount)*4));
                        paramCount += 1;
                    }
                    fArgs = fArgs->next;
                }
                

                while( (t->next != NULL) && 
                        ( (t->next->type == TAC_VARI) || (t->next->type == TAC_SYMBOL) )
                        ) {
                    t = t->next;
                    if(t->type == TAC_VARI) {
                        //fprintf(out,"\n    movl $%s, %s",t->target->value, t->op1->value);
                        fprintf(out,"\n    subl $4, %%esp");//fprintf(out,"\n    subl $4, %%ebp");
                        fprintf(out,"\n    movl %s, %%eax",accessVar(t->target));
                        fprintf(out,"\n    movl %%eax, %d(%%ebp)",paramCount*4+8);
                        
                        paramCount += 1;
                    }
                }
                
                paramCount = 0;
                break;
            
            case TAC_IFZ:
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op1));
                fprintf(out,"\n    testl	%%eax, %%eax");
                fprintf(out,"\n    je	.%s",t->target->value);
                break;
            
            case TAC_JUMP:
                fprintf(out,"\n    jmp	.%s",t->target->value);
                break;
            
            case TAC_CALL:
                fprintf(out,"\n    call	%s",t->op1->value);
                fprintf(out,"\n    movl %%eax, %s",accessVar(t->target));
                
                /*fArgs = t->op1->args;
                while(fArgs != NULL) {
                    arg = fArgs->v;
                    if(arg != NULL) {
                        fprintf(out,"\n    popl %%ecx");
                    }
                    fArgs = fArgs->next;
                }*/
                
                paramCount = 0;
                break;
            
            case TAC_ARG:
                //fprintf(out,"\n     pushl %s ",accessVar(t->target));
                /*
                if(t->type == SYMBOL_IDENTIFIER) {
                    fprintf(out,"\n     pushl %s ", t->target->value);
                }
                else if( (t->type == SYMBOL_LIT_TRUE) || (t->type == SYMBOL_LIT_FALSE) ) {
                    fprintf(out,"\n     pushl $_V%s ", t->target->value);
                }
                else {
                    fprintf(out,"\n     pushl $%s ", t->target->value);
                }*/
                
                fprintf(out,"\n    subl $4, %%esp");
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->target));
                fprintf(out,"\n    movl %%eax, %d(%%esp)",((paramCount)*4));
                
                paramCount += 1;
                break;
                
            case TAC_RET:
                fprintf(out,"\n     movl %s, %%eax",accessVar(t->target));
                fprintf(out,"\n     movl	%%ebp, %%esp");
                fprintf(out,"\n     popl	%%ebp");
                fprintf(out,"\n     ret");
                break;
            
            case TAC_PRINT:
                if(t->target->type == SYMBOL_LIT_STRING) {
                    fprintf(out,"\n    pushl   $.LCS%d   ",auxStringId);
                    fprintf(out,"\n    call    puts");
                    fprintf(out,"\n    addl  $4, %%esp");
                    auxStringId++;
                }
                else {
                    fprintf(out,"\n     movl    %s, %%eax ",accessVar(t->target));
                    fprintf(out,"\n     movl	%%eax, (%%esp) ");
                    fprintf(out,"\n     call    __FNA__printVar ");
                }
                break;
                
            case TAC_READ:
                fprintf(out,"\n     pushl $%s", t->target->value);
                fprintf(out,"\n     pushl $.LC0_FORMAT_IO");
                fprintf(out,"\n     call    scanf");
                fprintf(out,"\n     addl  $8, %%esp");
            
                /*fprintf(out,"\n     movl    %s, %%eax ",t->target->value);
                fprintf(out,"\n     movl	%%eax, (%%esp) ");
                fprintf(out,"\n     call    __FNA__readVar ");*/
                break;
                
            case TAC_VECW:
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op1));
                
                fprintf(out,"\n    movl %s, %%ebx",accessVar(t->op2));
                
                fprintf(out,"\n    movl %%ebx,  %s(,%%eax,4)",accessVar(t->target));
                break;
            
            case TAC_VECR:
                fprintf(out,"\n    movl %s, %%eax",accessVar(t->op2));
                //fprintf(out,"\n    sall	$2, %%eax");
                //fprintf(out,"\n    addl %s, %%eax",accessVar(t->op1));
                
                fprintf(out,"\n    movl	%s(,%%eax,4), %%eax",accessVar(t->op1));
                
                fprintf(out,"\n    movl %%eax, %s",accessVar(t->target));
                break;
        }
    }
}


void assembleVariableDeclarations(TAC *list, FILE *out) {
    TAC *t;
    
    // Some global definitions...
    fprintf(out,"\n.data");
    fprintf(
            out,
            "\n.globl _VTRUE \n"
            "_VTRUE: "
            "\n   .long 1"
            "\n"
            "\n.globl _VFALSE \n"
            "_VFALSE: "
            "\n   .long 0"
          );
          
    int auxStringId = 0;
    param *fArgs;
    row *arg;
    
    for(t = list; t != NULL; t = t->next) {
        switch (t->type) {
        
            case TAC_BEGINFUN:               
                fArgs = t->target->args;
                while(fArgs != NULL) {
                    arg = fArgs->v;
                    if(arg != NULL) {
                        fprintf(
                            out,
                            "\n.globl %s \n"
                            "%s: \n"
                            "   .long 0\n",
                            arg->value, arg->value
                        );
                    }
                    
                    fArgs = fArgs->next;
                }
                break;
        
            case TAC_PRINT:
                if(t->target->type == SYMBOL_LIT_STRING) {
                    fprintf(out,"\n.LCS%d:   ",auxStringId);
                    fprintf(out,"\n .string %s \n",t->target->value);
                    auxStringId++;
                }
                break;
                
            case TAC_VARI:
                // Declare variable.
                fprintf(
                    out,
                    "\n.globl %s \n"
                    "%s: \n",
                    t->op1->value, t->op1->value
                );
                // Declare type.
                char *type;
                if(t->op1->type == SYMBOL_LIT_INTEGER) {
                    type = ".long";
                }
                else {
                    type = ".long";
                }
                fprintf(out, "  %s", type);
                // Declare value.
                if( t->target->type == SYMBOL_LIT_TRUE ) {
                    fprintf(out, "  1\n");
                }
                else if( t->target->type == SYMBOL_LIT_FALSE ) {
                    fprintf(out, "  0\n");
                }
                else {
                    fprintf(out, "  %s\n",t->target->value);
                }
                break;
            
            case TAC_VECI:
                fprintf(
                    out,
                    "\n.globl %s \n"
                    "%s: \n",
                    t->target->value, t->target->value
                );
                while(t->type == TAC_VECI) {
                    fprintf(out, "  .long   %s \n", t->op1->value);
                    t = t->next;
                }
                break;
        }
    }
    
    // Take care of the rest:
    row *ptR = NULL;
    int i;
    // For every line.
    for(i=0; i < symbolsTable.size; i++) {
        ptR = symbolsTable.rows[i];
        // For every entry.
        while(ptR != NULL) {
            // If temporary:
            if( (ptR->value != NULL) && (ptR->value[0] == '_') ) {
                fprintf(
                    out,
                    "\n.globl %s \n"
                    "%s: \n"
                    "   .long   0 \n\n",
                    ptR->value, ptR->value
                );
            }
            
            ptR = ptR->next;
        }
    }
    
    
        
}
